use std::cell::{Cell, RefCell};
use std::rc::Rc;

use thunderdome::{Arena, Index};
use windows::core::{w, HSTRING, PCWSTR};
use windows::Win32::Foundation::{HMODULE, HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::Graphics::Gdi::ValidateRect;
use windows::Win32::System::Performance::{QueryPerformanceCounter, QueryPerformanceFrequency};
use windows::Win32::UI::Input::KeyboardAndMouse::{ReleaseCapture, SetCapture};
use windows::Win32::UI::WindowsAndMessaging::{
	CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage, RegisterClassW, SetWindowLongPtrW, CREATESTRUCTW,
	CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, GWLP_USERDATA, IDC_ARROW, WINDOW_EX_STYLE, WM_CHAR, WM_CREATE, WM_DESTROY, WM_IME_COMPOSITION,
	WM_IME_ENDCOMPOSITION, WM_IME_STARTCOMPOSITION, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP,
	WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCCREATE, WM_PAINT, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE, WM_SYSKEYDOWN, WM_SYSKEYUP,
	WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_SIZEBOX, WS_VISIBLE,
};

use crate::win::WinEvent;
use crate::{hiword, loword};

use super::{WinHandle, WinRef, Window};

pub type RcDynWinDelegate = Rc<dyn types::WinRep<Window>>;

pub struct Win {
	_key: String,
	rep: RcDynWinDelegate,
	hwnd: Cell<HWND>,
}
impl Win {
	unsafe fn proc(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<LRESULT> {
		let hwnd = self.hwnd.get();
		let winref = WinRef(&hwnd);
		match msg {
			WM_CREATE => {
				self.rep.on_create(winref);
			}
			WM_DESTROY => {
				self.rep.on_destroy(winref);
				PostQuitMessage(0);
			}
			WM_PAINT => {
				let mut nanos = 0;
				let mut freqs = 1;
				QueryPerformanceCounter(&mut nanos).unwrap();
				QueryPerformanceFrequency(&mut freqs).unwrap();
				let nano_freqs = 1000_000_000 / freqs;
				self.rep.on_req_draw(winref, nanos * nano_freqs);
				println!("WM_PAINT {nanos} {freqs}");
				// TODO remove
				// use windows::Win32::Graphics::Gdi::{BeginPaint, EndPaint, FillRect, COLOR_WINDOWFRAME, HBRUSH, PAINTSTRUCT};
				// let mut ps = PAINTSTRUCT::default();
				// let hdc = BeginPaint(hwnd, &mut ps);
				// FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOWFRAME.0 as _));
				// _ = EndPaint(hwnd, &ps);
				_ = ValidateRect(hwnd, None);
			}
			WM_SIZE => {
				let w: u32 = loword(lparam.0 as _) as _;
				let h: u32 = hiword(lparam.0 as _) as _;
				self.rep.on_resize(winref, (w, h));
				use types::WinRef as _;
				winref.fresh("WM_SIZE");
			}
			WM_USER => {
				self.rep.on_proxy(winref, lparam.0 as _);
			}
			WM_LBUTTONDOWN | WM_LBUTTONUP | WM_RBUTTONDOWN | WM_RBUTTONUP | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_XBUTTONDOWN
			| WM_XBUTTONUP | WM_MOUSEMOVE => {
				match msg {
					WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN | WM_XBUTTONDOWN => _ = SetCapture(hwnd),
					WM_LBUTTONUP | WM_RBUTTONUP | WM_MBUTTONUP | WM_XBUTTONUP => _ = ReleaseCapture(),
					_ => {}
				}
				self.rep.on_mouse(winref, WinEvent(&(), msg, wparam, lparam));
			}
			WM_KEYDOWN | WM_KEYUP => {
				self.rep.on_keyboard(winref, WinEvent(&(), msg, wparam, lparam));
			}
			WM_SYSKEYDOWN | WM_SYSKEYUP => {
				self.rep.on_keyboard(winref, WinEvent(&(), msg, wparam, lparam));
				return DefWindowProcW(hwnd, msg, wparam, lparam).into();
			}
			WM_MOUSEWHEEL | WM_MOUSEHWHEEL => {
				self.rep.on_wheel(winref, WinEvent(&(), msg, wparam, lparam));
			}
			WM_IME_STARTCOMPOSITION => {
				// println!("WM_IME_STARTCOMPOSITION {wparam:?}");
				self.rep.on_ime(winref, &types::Ime::begin());
				return DefWindowProcW(hwnd, msg, wparam, lparam).into();
			}
			WM_IME_COMPOSITION => {
				// println!("WM_IME_COMPOSITION {wparam:?}");
				let ime_context = unsafe { ImeContext::current(hwnd) };
				if let Some((s, _, _)) = ime_context.get_composing_text_and_cursor() {
					self.rep.on_ime(winref, &types::Ime::update(s.into()));
				}
			}
			WM_IME_ENDCOMPOSITION => {
				// println!("WM_IME_ENDCOMPOSITION {wparam:?}");
				let ime_context = unsafe { ImeContext::current(hwnd) };
				if let Some(s) = ime_context.get_composed_text() {
					self.rep.on_ime(winref, &types::Ime::end(s.into()));
				}
				return DefWindowProcW(hwnd, msg, wparam, lparam).into();
			}
			WM_CHAR => {
				// println!("WM_CHAR {wparam:?}");
				match wparam.0 {
					8 | 127 => _ = self.rep.on_ime(winref, &types::Ime::back()),
					v => {
						let s = char::from_u32(v as _).map(|v| v.to_string()).unwrap_or_default();
						self.rep.on_ime(winref, &types::Ime::end(s.into()));
					}
				}
			}
			_ => return DefWindowProcW(hwnd, msg, wparam, lparam).into(),
		}
		None
	}
}

pub type Key = Index;
thread_local! {
	static CACHE:RefCell<Arena<Win>> = Default::default();
	static WCLS:RefCell<Option<u16>> = Default::default();
}
pub(crate) fn _with_win_mut<T>(key: Key, mut fun: impl FnMut(&mut Win) -> Option<T>) -> Option<T> {
	CACHE.with_borrow_mut(|vars| vars.get_mut(key).map(|win| fun(win)).flatten())
}
pub(crate) fn with_win<T>(key: Key, mut fun: impl FnMut(&Win) -> Option<T>) -> Option<T> {
	CACHE.with_borrow(|vars| vars.get(key).map(|win| fun(win)).flatten())
}
pub(crate) fn _with_wins<T>(mut fun: impl FnMut(&Arena<Win>) -> T) -> T {
	CACHE.with_borrow(|vars: &Arena<Win>| fun(vars))
}

impl WinHandle {
	pub unsafe fn reg(instance: HMODULE) -> Option<PCWSTR> {
		WCLS.with_borrow_mut(|cls| {
			let window_class = w!("XLoopWindow");
			if cls.is_none() {
				let wc = WNDCLASSW {
					hCursor: LoadCursorW(None, IDC_ARROW).ok()?,
					hInstance: instance.into(),
					lpszClassName: window_class,

					style: CS_HREDRAW | CS_VREDRAW,
					lpfnWndProc: Some(wnd_proc),
					..Default::default()
				};

				let atom = RegisterClassW(&wc);
				debug_assert!(atom != 0);

				*cls = Some(atom);
			}
			Some(window_class)
		})
	}
	pub unsafe fn new(
		key: impl AsRef<str>,
		attr: <Window as types::Win>::Attr,
		rep: impl types::WinRep<Window> + 'static,
	) -> Option<WinHandle> {
		let instance = crate::app::AppHandle::singleton().0;

		let window_class = Self::reg(instance)?;

		let idx = CACHE.with_borrow_mut(|wins| {
			wins.insert(Win {
				_key: key.as_ref().to_string(),
				rep: Rc::new(rep),
				hwnd: Cell::new(HWND(0)),
			})
		});
		let title: HSTRING = attr.title.into();
		let (x, y) = attr.pos.unwrap_or((CW_USEDEFAULT, CW_USEDEFAULT));
		let (w, h) = attr
			.size
			.map(|(w, h)| (w as i32, h as i32))
			.unwrap_or((CW_USEDEFAULT, CW_USEDEFAULT));
		let mut style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
		if attr.closable {
			// not surport
		}
		if attr.maximizable {
			style |= WS_MAXIMIZEBOX;
		}
		if attr.minimizable {
			style |= WS_MINIMIZEBOX;
		}
		if attr.resizable {
			style |= WS_SIZEBOX;
		}
		let hwnd = CreateWindowExW(
			WINDOW_EX_STYLE::default(),
			window_class,
			&title,
			style,
			x,
			y,
			w,
			h,
			None,
			None,
			instance,
			Some(idx.to_bits() as _),
		);

		Some(WinHandle(hwnd))
	}
}
extern "system" fn wnd_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
	unsafe {
		let userdata = GetWindowLongPtrW(hwnd, GWLP_USERDATA);
		let Some(key) = (match (userdata, msg) {
			(0, WM_NCCREATE) => {
				let createstruct = &mut *(lparam.0 as *mut CREATESTRUCTW);
				SetWindowLongPtrW(hwnd, GWLP_USERDATA, createstruct.lpCreateParams as _);
				// log::info!("WM_NCCREATE");
				return DefWindowProcW(hwnd, msg, wparam, lparam);
			}
			(0, _) => {
				// log::info!("BEFORE WM_NCCREATE : {}", msg);
				return DefWindowProcW(hwnd, msg, wparam, lparam);
			}
			_ => Key::from_bits(userdata as _),
		}) else {
			// log::warn!("EMPTY KEY : {}", msg);
			return DefWindowProcW(hwnd, msg, wparam, lparam);
		};
		with_win(key, |win| {
			if win.hwnd.get() == HWND(0) {
				win.hwnd.set(hwnd);
			}
			win.proc(msg, wparam, lparam)
		})
		.unwrap_or(LRESULT(0))
	}
}
mod ime {
	use std::ffi::c_void;

	use windows::Win32::{
		Foundation::{HWND, POINT, RECT},
		Globalization::HIMC,
		UI::{
			Input::Ime::{
				ImmAssociateContextEx, ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext, ImmSetCandidateWindow,
				ImmSetCompositionWindow, ATTR_TARGET_CONVERTED, ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM, CFS_EXCLUDE, CFS_POINT,
				COMPOSITIONFORM, GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN, IACE_DEFAULT,
				IME_COMPOSITION_STRING,
			},
			WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED},
		},
	};
	pub struct ImeContext {
		hwnd: HWND,
		himc: HIMC,
	}
	impl ImeContext {
		pub unsafe fn current(hwnd: HWND) -> Self {
			let himc = unsafe { ImmGetContext(hwnd) };
			ImeContext { hwnd, himc }
		}

		pub unsafe fn get_composing_text_and_cursor(&self) -> Option<(String, Option<usize>, Option<usize>)> {
			let text = unsafe { self.get_composition_string(GCS_COMPSTR.0) }?;
			let attrs = unsafe { self.get_composition_data(GCS_COMPATTR.0) }.unwrap_or_default();

			let mut first = None;
			let mut last = None;
			let mut boundary_before_char = 0;

			for (attr, chr) in attrs.into_iter().zip(text.chars()) {
				let char_is_targeted = attr as u32 == ATTR_TARGET_CONVERTED || attr as u32 == ATTR_TARGET_NOTCONVERTED;

				if first.is_none() && char_is_targeted {
					first = Some(boundary_before_char);
				} else if first.is_some() && last.is_none() && !char_is_targeted {
					last = Some(boundary_before_char);
				}

				boundary_before_char += chr.len_utf8();
			}

			if first.is_some() && last.is_none() {
				last = Some(text.len());
			} else if first.is_none() {
				// IME haven't split words and select any clause yet, so trying to retrieve normal
				// cursor.
				let cursor = unsafe { self.get_composition_cursor(&text) };
				first = cursor;
				last = cursor;
			}

			Some((text, first, last))
		}

		pub unsafe fn get_composed_text(&self) -> Option<String> {
			unsafe { self.get_composition_string(GCS_RESULTSTR.0) }
		}

		unsafe fn get_composition_cursor(&self, text: &str) -> Option<usize> {
			let cursor = unsafe { ImmGetCompositionStringW(self.himc, GCS_CURSORPOS, None, 0) };
			(cursor >= 0).then(|| text.chars().take(cursor as _).map(|c| c.len_utf8()).sum())
		}

		unsafe fn get_composition_string(&self, gcs_mode: u32) -> Option<String> {
			let data = unsafe { self.get_composition_data(gcs_mode) }?;
			let (prefix, _shorts, suffix) = unsafe { data.align_to::<u16>() };
			if prefix.is_empty() && suffix.is_empty() {
				#[cfg(target_os = "windows")]
				use std::os::windows::prelude::OsStringExt;
				#[cfg(target_os = "windows")]
				return std::ffi::OsString::from_wide(_shorts).into_string().ok();
			}
			None
		}

		unsafe fn get_composition_data(&self, gcs_mode: u32) -> Option<Vec<u8>> {
			let size = match unsafe { ImmGetCompositionStringW(self.himc, IME_COMPOSITION_STRING(gcs_mode), None, 0) } {
				0 => return Some(Vec::new()),
				size if size < 0 => return None,
				size => size,
			};

			let mut buf = Vec::<u8>::with_capacity(size as _);
			let size = unsafe {
				ImmGetCompositionStringW(
					self.himc,
					IME_COMPOSITION_STRING(gcs_mode),
					Some(buf.as_mut_ptr() as *mut c_void),
					size as _,
				)
			};

			if size < 0 {
				None
			} else {
				unsafe { buf.set_len(size as _) };
				Some(buf)
			}
		}

		pub unsafe fn set_ime_cursor_area(&self, spot: (i32, i32), size: (i32, i32), _scale_factor: f64) {
			if !unsafe { ImeContext::system_has_ime() } {
				return;
			}

			let (x, y) = spot; //.to_physical::<i32>(scale_factor).into();
			let (width, height): (i32, i32) = size; //.to_physical::<i32>(scale_factor).into();
			let rc_area = RECT {
				left: x,
				top: y,
				right: x + width,
				bottom: y + height,
			};
			let candidate_form = CANDIDATEFORM {
				dwIndex: 0,
				dwStyle: CFS_EXCLUDE,
				ptCurrentPos: POINT { x, y },
				rcArea: rc_area,
			};
			let composition_form = COMPOSITIONFORM {
				dwStyle: CFS_POINT,
				ptCurrentPos: POINT { x, y: y + height },
				rcArea: rc_area,
			};

			unsafe {
				_ = ImmSetCompositionWindow(self.himc, &composition_form);
				_ = ImmSetCandidateWindow(self.himc, &candidate_form);
			}
		}

		pub unsafe fn set_ime_allowed(hwnd: HWND, allowed: bool) {
			if !unsafe { ImeContext::system_has_ime() } {
				return;
			}
			let context = Self::current(hwnd);

			if allowed {
				_ = unsafe { ImmAssociateContextEx(hwnd, context.himc, IACE_DEFAULT) };
			} else {
				_ = unsafe { ImmAssociateContextEx(hwnd, context.himc, IACE_CHILDREN) };
			}
		}

		unsafe fn system_has_ime() -> bool {
			unsafe { GetSystemMetrics(SM_IMMENABLED) != 0 }
		}
	}

	impl Drop for ImeContext {
		fn drop(&mut self) {
			_ = unsafe { ImmReleaseContext(self.hwnd, self.himc) };
		}
	}
}
pub use ime::*;
